package com.veetao.api.controller;

import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import com.veetao.api.annotation.McpReq;
import com.veetao.api.annotation.Required;
import com.veetao.api.config.LocaleHandler;
import com.veetao.api.consts.HttpConstants;
import com.veetao.api.consts.McpConstants;
import com.veetao.api.filter.ApiRequestChecker;
import com.veetao.api.filter.AuthorizationChecker;
import com.veetao.api.filter.CommonParamsChecker;
import com.veetao.api.filter.MethodNameChecker;
import com.veetao.api.filter.MethodNameFrequencyChecker;
import com.veetao.api.filter.SignatureFrequencyChecker;
import com.veetao.api.filter.UserActionFrequencyChecker;
import com.veetao.api.filter.UserActionPermissionChecker;
import com.veetao.api.model.ApiCommand;
import com.veetao.api.model.ApiCommandContext;
import com.veetao.api.model.ApiException;
import com.veetao.api.model.ApiResult;
import com.veetao.api.model.ApiResultCode;
import com.veetao.api.model.AppClientInfo;
import com.veetao.api.model.RequiredCheck;
import com.veetao.api.model.RequiredCheck.ErrorType;
import com.veetao.api.resp.FailResultResp;
import com.veetao.api.service.CommandLookupService;
import com.veetao.api.service.MobileClientAppService;
import com.veetao.api.service.PassportService;
import com.veetao.api.service.impl.UserActionManageService;
import com.veetao.api.utils.FieldLoadUtils;
import com.veetao.api.utils.McpUtils;
import com.veetao.api.utils.ModelCastUtils2;
import com.veetao.api.utils.RequestUtils;
import com.veetao.api.utils.RequiredCheckUtils;

@Controller
@RequestMapping(method = { RequestMethod.GET, RequestMethod.POST })
public class ApiController implements InitializingBean {
	/**
	 * Logger for this class
	 */
	private static final Log logger = LogFactory.getLog(ApiController.class);
	private static final Log httpAccessLogger = LogFactory.getLog("http_access");
	private static final Log httpAccessSlowLogger = LogFactory.getLog("http_access_slow");

	private static final long COST_TIME_LIMIT = 1000;
	private final String httpAccessLogFormat = "%s|%s|%s|%s|%s|%s|%s|%s|%s";

	@Autowired
	private MobileClientAppService mobileClientAppService;

	@Autowired
	private CommandLookupService commandLookupService;
	
	@Resource(name="passportService")
	private PassportService passportService;
	
	@Resource
	private UserActionManageService userActionManageService;
	
	@Resource
	private LocaleHandler localeHandler;
	
	
	@Resource
	private List<ApiRequestChecker> checkerList;

	@RequestMapping("/api/**")
	public ApiResult api() {
		final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		final HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getResponse();
		long curTime = System.currentTimeMillis();
		long userIdLog = 0;
		String clientIpLog = "";
		String userAgentLog = "";
		String methodNameLog = "";
		
		
		/*
		 * 检查参数及权限
		 */
		ApiResult result = onPreRequest(request);
		if(!isResultOK(result)) {
			return result;
		}
		
		localeHandler.handleLocaleInfo(request);
		
		try {
			clientIpLog = McpUtils.getRemoteAddrAll(request);
			userAgentLog = request.getHeader("User-Agent");
			
			// 构造参数
			Map<String, String> requestParamMap = McpUtils.fillParamMap(request);
			request.setAttribute(McpConstants.REQ_ATTR_PerSMAP, requestParamMap);

			
			String methodName = McpUtils.getCmdMethodFromURI(request.getRequestURI());
			String version = RequestUtils.get(request, HttpConstants.PARAM_VER);
			Map<String, String> httpHeads = McpUtils.getHttpHeads(request);
			
			// form方式上传文件
			Map<String, Object> binaryParams = null;
			if (request instanceof MultipartHttpServletRequest) {
				MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
				binaryParams = new HashMap<String, Object>();
				for (Map.Entry<String, MultipartFile> e : multipartRequest.getFileMap().entrySet()) {
					binaryParams.put(e.getKey(), e.getValue());
				}
			}
			int appId = NumberUtils.toInt(RequestUtils.get(request, HttpConstants.PARAM_APP_ID));
			int userId = NumberUtils.toInt(RequestUtils.get(request, HttpConstants.PARAM_UID));
			String ticket = RequestUtils.get(request, HttpConstants.PARAM_TICKET);
			String appVersion = RequestUtils.get(request, HttpConstants.PARAM_VERSION);
			AppClientInfo appInfo = mobileClientAppService.getAppInfo(appId);
			
			ApiCommandContext context = new ApiCommandContext(curTime, 
					appInfo, 
					userId, 
					requestParamMap, 
					binaryParams, 
					ticket, 
					methodName, 
					httpHeads, 
					clientIpLog,
					appVersion);
			
			context.setLocale(RequestUtils.get(request, HttpConstants.PARAM_LOCALE));
			
			context.setHttpServletRequest(request);
			context.setHttpServletResponse(response);
			
			/*
			 * 根据参数查找命令对象并执行 
			 */
			ApiCommand apiCommand = commandLookupService.lookupApiCommand(methodName, version);
			return executeCommand(apiCommand, request,response,context);
		} catch (NumberFormatException nfe) {
			logger.error("Param parse error", nfe);
			result = new ApiResult(ApiResultCode.E_SYS_PARAM);
		} catch (Throwable e) {
			logger.error("CommandController handleRequestInternal", e);
			result = new ApiResult(ApiResultCode.E_SYS_UNKNOWN);
		} finally { // log统计
			final long time = System.currentTimeMillis();
			final long costTime = time - curTime;
			final String methodName = StringUtils.defaultString(request.getMethod());
			try {
				final String httpAccessLogMsg = String.format(httpAccessLogFormat//
						, time + "" // 时间
						, request.getRequestURL() // url
						, userIdLog + "" // userId
						, methodName // http method
						, StringUtils.defaultString(userAgentLog) // http agent
						, StringUtils.defaultString(clientIpLog) // clientIp
						, StringUtils.defaultString(methodNameLog) // mcpMethodName
						, costTime + "" // 消耗的时间
						, result.getCode() + "");
				httpAccessLogger.error(httpAccessLogMsg);
				if (costTime >= COST_TIME_LIMIT) {
					httpAccessSlowLogger.info(httpAccessLogMsg);
				}
			} catch (Exception e) {
				logger.error("finally method name:" + methodNameLog, e);
			}
		}
		
		return result;

	}

	private static final String SF_REQ_ERROR = "checkReqModel: userId[%s], sig[%s], %s: %s [%s]";
	/**
	 * 对请求对象的自定义检查
	 * 
	 * @param reqModel
	 * @param mcpReq
	 * @param ctx
	 * @throws ApiException
	 */
	private void checkReqModel(Object reqModel, McpReq mcpReq, ApiCommandContext ctx) throws ApiException {
		RequiredCheck result = null;
		if (mcpReq != null) {// 按单个参数加注解的形式来处理
			result = RequiredCheckUtils.check(mcpReq, reqModel);
		} else {
			result = RequiredCheckUtils.checkMonolayer(reqModel);
		}
		if (result != null && result.getErrorType() != ErrorType.Pass) {
			logger.error(String.format(SF_REQ_ERROR, ctx.getUserId(), ctx.getSig(), ctx.getMethodName(),
					result.toMessage(), FieldLoadUtils.toString(result.getValue())));
			Required required = result.getRequired();
			if (required != null && required.errorCode() > 0) {
				int errorCode = required.errorCode();
				String errorMsg = required.errorMsg();
				if (errorMsg == null || errorMsg.isEmpty()) {
					throw new ApiException(errorCode);
				}
				throw new ApiException(errorCode, errorMsg);
			}
			if (mcpReq != null && mcpReq.errorCode() > 0) {
				int errorCode = mcpReq.errorCode();
				String errorMsg = mcpReq.errorMsg();
				if (errorMsg == null || errorMsg.isEmpty()) {
					throw new ApiException(errorCode);
				}
				throw new ApiException(errorCode, errorMsg);
			}
			throw new ApiException(ApiResultCode.E_SYS_PARAM);
		}
	}

	private static final String SF_RESP_ERROR = "checkRespModel: userId[%s], sig[%s], %s: %s [%s]";

	/**
	 * 对结果对象的自定义检查
	 * 
	 * @param respModel
	 * @param ctx
	 * @throws ApiException
	 */
	private void checkRespModel(Object respModel, ApiCommandContext ctx) throws ApiException {
		RequiredCheck result = RequiredCheckUtils.checkMultilayer(respModel);
		if (result != null && result.getErrorType() != ErrorType.Pass) {
			logger.error(String.format(SF_RESP_ERROR//
					, ctx.getUserId()//
					, ctx.getSig()//
					, ctx.getMethodName()//
					, result.toMessage()//
					, FieldLoadUtils.toString(result.getValue())));
			throw new ApiException(ApiResultCode.E_SYS_PARAM);
		}
	}
	
	public static final ApiResult SYS_RPC_ERROR = new ApiResult(ApiResultCode.E_SYS_RPC_ERROR);
	public static final ApiResult API_RESULT_SUCCESS = new ApiResult(ApiResultCode.SUCCESS, 0);
	private ApiResult executeCommand(ApiCommand apiCommand,
			HttpServletRequest req, HttpServletResponse resp,
			ApiCommandContext ctx) {
		if (logger.isDebugEnabled()) {
			logger.debug("onExecute(ApiCommandContext) - start");
		}
		final String sig = ctx.getSig() == null ? "null" : ctx.getSig();
		long execBeginTime = System.currentTimeMillis();
		
		/*
		 * support method with raw request and raw response parameters 
		 */
		try {
			if(apiCommand.isWithRawReqAndResp()) {
				Object respModel = apiCommand.getMethod().invoke(apiCommand.getTargetObject(), req,resp, ctx);
				if(respModel instanceof Void) {
					return null;
				}
				ApiResult result = new ApiResult(ApiResultCode.SUCCESS, respModel);
				return result;
			}
			
			if(apiCommand.notNeedParameter()) {
				Object respModel = apiCommand.getMethod().invoke(apiCommand.getTargetObject(), ctx);
				if (apiCommand.isVoidMethod()) {// 无返回结果指令不用检查
					return API_RESULT_SUCCESS;
				}
				this.checkRespModel(respModel, ctx);
				return new ApiResult(ApiResultCode.SUCCESS, respModel);
			}
			
			
			Object reqModel = deduceRequestModel(apiCommand, ctx);
			if(reqModel == null) {
				throw ApiException.E_SYS_INVALID_PARAM;
			}
			
			checkReqModel(reqModel, apiCommand.getMcpReq(), ctx);// 请求对象检查：参数必要性，值域
			
			try {
				Object respModel = apiCommand.getMethod().invoke(apiCommand.getTargetObject(), reqModel, ctx);
				if (apiCommand.isVoidMethod()) {// 无返回结果指令不用检查
					return API_RESULT_SUCCESS;
				}
				this.checkRespModel(respModel, ctx);
				return new ApiResult(ApiResultCode.SUCCESS, respModel);
			} catch (InvocationTargetException e) {
				throw e.getCause();
			}
		} catch (ApiException e) {
			String errorMsg = e.getMessage();
			if (errorMsg == null) {
				if (logger.isDebugEnabled()) {
					final String rfLogError = "ApiException: userId[%s], sig[%s] errorCode[%s]";
					final String logError = String.format(rfLogError, ctx.getUserId(), ctx.getSig(), e.getErrorCode());
					logger.debug(logError, e);
				}
				ApiResult result = new ApiResult(e.getErrorCode());
				return result;
			}
			ApiResult result = new ApiResult(e.getErrorCode(), errorMsg);
			if (e.getExt() != null) {
				result.setExt(e.getExt());
			}
			return result;
		} catch (Throwable t) {
			logger.error("onExecute(ApiCommandContext) sig:" + sig, t);
		} finally {
			McpUtils.rpcTimeCost(execBeginTime, ctx.getMethodName());
		}
		return SYS_RPC_ERROR;
	}


	/**
	 * 根据请求参数生成请求对象
	 */
	private Object deduceRequestModel(ApiCommand apiCommand, ApiCommandContext ctx) {
		if (apiCommand.getMcpReq() != null) {// 参数级注解，基本数据解析
			Object reqModel = ModelCastUtils2.mappingParameter(ctx.getStringParams(), apiCommand.getMcpReq(),
					apiCommand.getMcpReqType());
			if (reqModel != null) {
				return reqModel;
			}
			
			return ModelCastUtils2.mappingParameter(ctx.getBinaryParams(), apiCommand.getMcpReq(),
						apiCommand.getMcpReqType());
		}
		
		if (ctx.getBinaryParams() != null) {
			return ModelCastUtils2.mappingNew(ctx.getBinaryParams(), apiCommand.getReqClass());
		}
		
		return ModelCastUtils2.mappingNew(ctx.getStringParams(), apiCommand.getReqClass());
	}


	private boolean isResultOK(ApiResult result) {
		return result.getCode() == ApiResultCode.SUCCESS && !FailResultResp.class.isInstance(result.getData());
	}
	
	
	private ApiResult onPreRequest(HttpServletRequest req) {
		for(ApiRequestChecker checker : checkerList) {
			
			ApiResult result = checker.check(req);
			if(!isResultOK(result)) {
				return result;
			}
			
		}
		return new ApiResult(ApiResultCode.SUCCESS);
		
	}
	
	
	
	@Override
	public void afterPropertiesSet() throws Exception {
		Set<String> frameworkCheckers = new HashSet<>();
		String[] checkerNames = new String[]{
				CommonParamsChecker.class.getName(),
				AuthorizationChecker.class.getName(),
				MethodNameChecker.class.getName(),
				MethodNameFrequencyChecker.class.getName(),
				SignatureFrequencyChecker.class.getName(),
				UserActionFrequencyChecker.class.getName(),
				UserActionPermissionChecker.class.getName()
				};
		Collections.addAll(frameworkCheckers, checkerNames);
		
	
		Collections.sort(this.checkerList, new Comparator<ApiRequestChecker>() {

			@Override
			public int compare(ApiRequestChecker o1, ApiRequestChecker o2) {
				return o1.getOrder() - o2.getOrder();
			}
			
		});
		
		int lastOrder = -1;
		for(ApiRequestChecker c : checkerList) {
			if(c.getOrder() < 100 && !frameworkCheckers.contains(c.getClass().getName())) {
				// 自定义checker 设置的order小于允许的最小值
				throw new IllegalStateException("Custom checker must have an order greater than 100: " + c.getClass().getName());
				
			}
			if(lastOrder == c.getOrder()) {
				throw new IllegalStateException("Duplicated check order: " + c.getOrder() + ":" + c.getClass().getName());
			}
			
			lastOrder = c.getOrder();
			
		}
	}
	
	
	

}
